package com.yingwei.mining.simulator;

import java.util.Date;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.yingwei.mining.control.Constant;
import com.yingwei.mining.control.Data;
import com.yingwei.mining.model.businessobject.Edge;
import com.yingwei.mining.model.businessobject.Excavator;
import com.yingwei.mining.model.businessobject.Node;
import com.yingwei.mining.model.businessobject.Route;
import com.yingwei.mining.model.businessobject.Truck;
import com.yingwei.mining.model.businessobject.UnloadArea;
import com.yingwei.mining.model.businessobject.WorkOrder;
import com.yingwei.mining.util.LocationUtil;

public class TruckRobot
{
    private Logger logger = LoggerFactory.getLogger(TruckRobot.class);
    private WorkOrder workOrder = null;
    private Data data = Data.getData();
    TruckMemory truckMemory;
    private Truck truck;
    private Edge loadEdge;
    private Edge unloadEdge;

    public TruckRobot(TruckMemory truckMemory)
    {
	this.truckMemory = truckMemory;
	truck = (Truck) data.load(Truck.class, truckMemory.getTruckId());
	if (truckMemory.getLoadEdgeId() != -1)
	{
	    loadEdge = (Edge) data
		    .load(Edge.class, truckMemory.getLoadEdgeId());
	}
	else
	{
	    loadEdge = null;
	}
	if (truckMemory.getUnloadEdgeId() != -1)
	{
	    unloadEdge = (Edge) data.load(Edge.class, truckMemory
		    .getUnloadEdgeId());
	}
	else
	{
	    unloadEdge = null;
	}
    }

    public TruckMemory run()
    {
	workOrder = null;
	for (WorkOrder wo : data.getWorkOrders())
	{
	    if (wo.getTruck().getId() == truck.getId())
	    {
		if (wo.getStatus().equalsIgnoreCase(
			Constant.WORK_ORDER_STATUS_SCHEDULED))
		{
		    workOrder = wo;
		    break;
		}
	    }
	}
	if (workOrder == null)
	{
	    return truckMemory;
	}

	if (truck.getStatus().getStatusId().equals(
		Constant.TRUCK_STATUS_MOVE_TO_LOAD))
	{
	    truckMoveToLoad(workOrder);
	}
	else if (truck.getStatus().getStatusId().equals(
		Constant.TRUCK_STATUS_WAIT_LOAD))
	{
	    truckWaitLoad(workOrder);
	}
	else if (truck.getStatus().getStatusId().equals(
		Constant.TRUCK_STATUS_LOAD))
	{
	    truckLoad(workOrder);
	}
	else if (truck.getStatus().getStatusId().equals(
		Constant.TRUCK_STATUS_MOVE_TO_UNLOAD))
	{
	    truckMoveToUnload(workOrder);
	}
	else if (truck.getStatus().getStatusId().equals(
		Constant.TRUCK_STATUS_WAIT_UNLOAD))
	{
	    truckWaitUnload(workOrder);
	}
	else if (truck.getStatus().getStatusId().equals(
		Constant.TRUCK_STATUS_UNLOAD))
	{
	    truckUnload(workOrder);
	}
	if (loadEdge != null)
	{
	    truckMemory.loadEdgeId = loadEdge.getId();
	}
	else
	{
	    truckMemory.loadEdgeId = -1;
	}
	if (unloadEdge != null)
	{
	    truckMemory.unloadEdgeId = unloadEdge.getId();
	}
	else
	{
	    truckMemory.unloadEdgeId = -1;
	}
	return truckMemory;
    }

    void truckMoveToLoad(WorkOrder workOrder)
    {
	int x = truck.getX();
	int y = truck.getY();
	long lastModifyTime = truck.getModifyTime().getTime();
	long now = System.currentTimeMillis();
	// long timeOfCurrentProcess = now;
	// if the truck is already at excavator location, just change its status
	if (workOrder.getLoadRoute().getNodeFrom().getId() == workOrder
		.getLoadRoute().getNodeTo().getId())
	{
	    // workOrder.setLoadActualArrivalTime(new
	    // Date(timeOfCurrentProcess));
	    workOrder.setLoadActualArrivalTime(new Date());
	    data.update(workOrder);
	    truck.setModifyTime(new Date());
	    truck.setStatus(data
		    .getTruckStatus(Constant.TRUCK_STATUS_WAIT_LOAD));
	    data.update(truck);
	    data.flush();
	    return;
	}
	Route loadRoute = (Route) data.load(Route.class, workOrder
		.getLoadRoute().getId());
	if (loadEdge == null)
	{
	    // simply move the truck to the nearest node. this will
	    // happen at initial state.
	    // Node node = LocationUtil.findNearbyNode(x, y,
	    // loadRoute.getEdges());
	    Node node = loadRoute.getNodeFrom();
	    if (node == null)
	    {
		logger.warn("work order load route is empty for workOrder:"
			+ workOrder.getId());
	    }
	    x = node.getX();
	    y = node.getY();
	    loadEdge = findOnEdge(node.getX(), node.getY(), loadRoute
		    .getEdges());
	}
	if (loadEdge == null)
	{
	    loadEdge = findOnEdge(x, y, loadRoute.getEdges());
	}
	// calculate next position.
	double speed = truck.getModel().getCruisingSpeed();
	double deltaDistance = speed * (now - lastModifyTime) / 3600;
	double dist = LocationUtil.distanceToPoint(x, y, loadEdge.getToNode()
		.getX(), loadEdge.getToNode().getY());
	int nextPositionX = 0;
	int nextPositionY = 0;

	if (dist > 0.0)
	{
	    if (deltaDistance >= dist)
	    {
		// reached the toNode of this edge.
		// timeOfCurrentProcess = (long) ((now - lastModifyTime) * dist
		// / deltaDistance)
		// + lastModifyTime;
		nextPositionX = loadEdge.getToNode().getX();
		nextPositionY = loadEdge.getToNode().getY();
		if (loadEdge.getToNode().getId() == workOrder.getLoadRoute()
			.getNodeTo().getId())
		{
		    truck.setStatus(data
			    .getTruckStatus(Constant.TRUCK_STATUS_WAIT_LOAD));
		}
		else
		{
		    // set the current edge to nest edge
		    for (Edge eg : loadRoute.getEdges())
		    {
			if (loadEdge.getToNode().getId() == eg.getFromNode()
				.getId())
			{
			    loadEdge = eg;
			    break;
			}
		    }
		}
	    }
	    else
	    {
		nextPositionX = (int) ((loadEdge.getToNode().getX() - x)
			* (deltaDistance / dist) + x);
		nextPositionY = (int) ((loadEdge.getToNode().getY() - y)
			* (deltaDistance / dist) + y);
	    }
	    truck.setX(nextPositionX);
	    truck.setY(nextPositionY);
	    // truck.setModifyTime(new Date(timeOfCurrentProcess));
	    truck.setModifyTime(new Date());
	    data.update(truck);
	    // workOrder.setLoadActualArrivalTime(new
	    // Date(timeOfCurrentProcess));
	    workOrder.setLoadActualArrivalTime(new Date());
	    data.update(workOrder);
	    data.flush();
	}
	else
	{
	    // reached the destination, assertion is x,y is at the excavator
	    // position.
	    if (workOrder.getLoadRoute().getNodeTo().getX() != x
		    || workOrder.getLoadRoute().getNodeTo().getY() != y)
	    {
		logger
			.error("reached the destination, but x,y is not at the excavator position.");
	    }
	    truck.setModifyTime(new Date());
	    truck.setStatus(data
		    .getTruckStatus(Constant.TRUCK_STATUS_WAIT_LOAD));
	    data.update(truck);
	    // workOrder.setLoadActualArrivalTime(new
	    // Date(timeOfCurrentProcess));
	    workOrder.setLoadActualArrivalTime(new Date());
	    data.update(workOrder);
	    data.flush();
	}
    }

    void truckWaitLoad(WorkOrder workOrder)
    {
	Excavator excavator = workOrder.getExcavator();
	long earliestArrivalTime = Long.MAX_VALUE;
	WorkOrder earliestWorkOrder = null;
	for (WorkOrder wo : data.getWorkOrders())
	{
	    // wo = (WorkOrder) data.load(WorkOrder.class, wo.getId());
	    if (wo.getExcavator().getId() == excavator.getId()
		    && wo.getStatus().equalsIgnoreCase(
			    Constant.WORK_ORDER_STATUS_SCHEDULED)
		    && excavator.getStatus().getStatusId().equalsIgnoreCase(
			    Constant.EXCAVATOR_STATUS_IDLE)
		    && getTruckStatus(wo.getTruck().getId()).equalsIgnoreCase(
			    Constant.TRUCK_STATUS_WAIT_LOAD))
	    {
		if (wo.getLoadActualArrivalTime() != null)
		{
		    if (wo.getLoadActualArrivalTime().getTime() < earliestArrivalTime)
		    {
			earliestArrivalTime = wo.getLoadActualArrivalTime()
				.getTime();
			earliestWorkOrder = wo;
		    }
		}
	    }
	    // data.evict(wo.getTruck());
	}
	if (earliestWorkOrder != null
		&& earliestWorkOrder.getId() == workOrder.getId())
	{
	    workOrder.setLoadActualStartTime(new Date());
	    data.update(workOrder);
	    truck.setStatus(data.getTruckStatus(Constant.TRUCK_STATUS_LOAD));
	    data.update(truck);
	    excavator.setStatus(data
		    .getExcavatorStatus(Constant.EXCAVATOR_STATUS_BUSY));
	    data.update(excavator);
	    data.flush();
	}
    }

    void truckLoad(WorkOrder workOrder)
    {
	long now = System.currentTimeMillis();
	long loadingTime = workOrder.getLoadScheduleEndTime().getTime()
		- workOrder.getLoadScheduleStartTime().getTime();
	if (workOrder.getLoadActualStartTime() != null
		&& workOrder.getLoadActualStartTime().getTime() + loadingTime < now)
	{
	    workOrder.setLoadActualEndTime(new Date());
	    data.update(workOrder);
	    truck.setStatus(data
		    .getTruckStatus(Constant.TRUCK_STATUS_MOVE_TO_UNLOAD));
	    data.update(truck);
	    workOrder.getExcavator().setStatus(
		    data.getExcavatorStatus(Constant.EXCAVATOR_STATUS_IDLE));
	    data.update(workOrder.getExcavator());
	    data.flush();
	}
    }

    void truckMoveToUnload(WorkOrder workOrder)
    {
	int x = truck.getX();
	int y = truck.getY();
	long lastModifyTime = truck.getModifyTime().getTime();
	long now = System.currentTimeMillis();
	// long timeOfCurrentProcess = now;
	if (workOrder.getUnloadRoute().getNodeFrom().getId() == workOrder
		.getUnloadRoute().getNodeTo().getId())
	{
	    workOrder.setUnloadActualArrivalTime(new Date());
	    data.update(workOrder);
	    truck.setModifyTime(new Date());
	    truck.setStatus(data
		    .getTruckStatus(Constant.TRUCK_STATUS_WAIT_UNLOAD));
	    data.update(truck);
	    data.flush();
	    return;
	}
	// refresh the route from database
	Route unloadRoute = (Route) data.load(Route.class, workOrder
		.getUnloadRoute().getId());
	if (unloadEdge == null)
	{
	    // simply move the truck to the nearest node.
	    // Node node = LocationUtil.findNearbyNode(x, y,
	    // unloadRoute.getEdges());
	    Node node = unloadRoute.getNodeFrom();
	    if (node == null)
	    {
		logger.warn("work order unload route is empty for workOrder:"
			+ workOrder.getId());
	    }
	    x = node.getX();
	    y = node.getY();
	    unloadEdge = findOnEdge(x, y, unloadRoute.getEdges());
	    if (unloadEdge == null)
	    {
		logger.warn("work order unload route is empty for workOrder:"
			+ workOrder.getId());
	    }
	}
	// calculate next position.
	double speed = truck.getModel().getCruisingSpeedWithLoad();
	double deltaDistance = speed * (now - lastModifyTime) / 3600;
	double dx = x - unloadEdge.getToNode().getX();
	double dy = y - unloadEdge.getToNode().getY();
	double dist = Math.sqrt(dx * dx + dy * dy);
	int nextPositionX = 0;
	int nextPositionY = 0;

	if (dist > 0.0)
	{
	    if (deltaDistance > dist)
	    {
		// timeOfCurrentProcess = (long) ((now - lastModifyTime) * dist
		// / deltaDistance)
		// + lastModifyTime;
		nextPositionX = unloadEdge.getToNode().getX();
		nextPositionY = unloadEdge.getToNode().getY();
		if (unloadEdge.getToNode().getId() == workOrder
			.getUnloadRoute().getNodeTo().getId())
		{
		    truck.setStatus(data
			    .getTruckStatus(Constant.TRUCK_STATUS_WAIT_UNLOAD));
		}
		else
		{
		    // set the current edge to nest edge
		    for (Edge eg : unloadRoute.getEdges())
		    {
			if (unloadEdge.getToNode().getId() == eg.getFromNode()
				.getId())
			{
			    unloadEdge = eg;
			    break;
			}
		    }
		}
	    }
	    else
	    {
		nextPositionX = (int) ((unloadEdge.getToNode().getX() - x)
			* (deltaDistance / dist) + x);
		nextPositionY = (int) ((unloadEdge.getToNode().getY() - y)
			* (deltaDistance / dist) + y);
	    }
	    truck.setX(nextPositionX);
	    truck.setY(nextPositionY);
	    truck.setModifyTime(new Date());
	    data.update(truck);
	    workOrder.setUnloadActualArrivalTime(new Date());
	    data.update(workOrder);
	    data.flush();
	}
	else
	{
	    // reached the destination, assertion is x,y is at the excavator
	    // position.
	    if (workOrder.getUnloadRoute().getNodeTo().getX() != x
		    || workOrder.getUnloadRoute().getNodeTo().getY() != y)
	    {
		logger
			.error("reached the destination, but x,y is not at the unload position.");
	    }
	    truck.setModifyTime(new Date());
	    truck.setStatus(data
		    .getTruckStatus(Constant.TRUCK_STATUS_WAIT_UNLOAD));
	    data.update(truck);
	    workOrder.setUnloadActualArrivalTime(new Date());
	    data.update(workOrder);
	    data.flush();
	}
    }

    void truckWaitUnload(WorkOrder workOrder)
    {
	UnloadArea unloadArea = workOrder.getUnloadArea();
	long earliestArrivalTime = Long.MAX_VALUE;
	WorkOrder earliestWorkOrder = null;
	for (WorkOrder wo : data.getWorkOrders())
	{
	    if (wo.getUnloadArea().getId() == unloadArea.getId()
		    && wo.getStatus().equalsIgnoreCase(
			    Constant.WORK_ORDER_STATUS_SCHEDULED)
		    && wo.getUnloadArea().getStatus().getStatusId()
			    .equalsIgnoreCase(Constant.UNLOAD_AREA_STATUS_IDLE)
		    && getTruckStatus(wo.getTruck().getId()).equalsIgnoreCase(
			    Constant.TRUCK_STATUS_WAIT_UNLOAD))
	    {
		if (wo.getUnloadActualArrivalTime() != null
			&& wo.getUnloadActualArrivalTime().getTime() < earliestArrivalTime)
		{
		    earliestArrivalTime = wo.getUnloadActualArrivalTime()
			    .getTime();
		    earliestWorkOrder = wo;
		}
	    }
	    // data.evict(wo.getTruck());
	}
	if (earliestWorkOrder != null
		&& earliestWorkOrder.getId() == workOrder.getId())
	{
	    workOrder.setUnloadActualStartTime(new Date());
	    data.update(workOrder);
	    truck.setStatus(data.getTruckStatus(Constant.TRUCK_STATUS_UNLOAD));
	    data.update(truck);
	    workOrder.getUnloadArea().setStatus(
		    data.getUnloadAreaStatus(Constant.UNLOAD_AREA_STATUS_BUSY));
	    data.update(workOrder.getUnloadArea());
	    data.flush();
	}
    }

    void truckUnload(WorkOrder workOrder)
    {
	long now = System.currentTimeMillis();
	long loadingTime = workOrder.getUnloadScheduleEndTime().getTime()
		- workOrder.getUnloadScheduleStartTime().getTime();
	if (workOrder.getUnloadActualStartTime() != null
		&& workOrder.getUnloadActualStartTime().getTime() + loadingTime < now)
	{
	    workOrder.setUnloadActualEndTime(new Date());
	    workOrder.setStatus(Constant.WORK_ORDER_STATUS_FINISHED);
	    data.update(workOrder);
	    truck.setStatus(data
		    .getTruckStatus(Constant.TRUCK_STATUS_WAIT_INSTRUCTION));
	    data.update(truck);
	    workOrder.getUnloadArea().setStatus(
		    data.getUnloadAreaStatus(Constant.UNLOAD_AREA_STATUS_IDLE));
	    data.update(workOrder.getUnloadArea());
	    data.flush();
	    loadEdge = null;
	    unloadEdge = null;
	}
    }

    /*
     * Node findOnNode(int x, int y, Set<Edge> edges) { Node nodeFound = null;
     * for (Edge edge : edges) { Node n = edge.getFromNode(); if (x >
     * LocationUtil.getScreenLocationX(n.getX()) -
     * Constant.LineSelectionDistance / 2 && x <
     * LocationUtil.getScreenLocationX(n.getX()) +
     * Constant.LineSelectionDistance && y >
     * LocationUtil.getScreenLocationY(n.getY()) -
     * Constant.LineSelectionDistance && y <
     * LocationUtil.getScreenLocationY(n.getY()) +
     * Constant.LineSelectionDistance) { nodeFound = n; break; } } return
     * nodeFound; }
     */

    /**
     * Find the edge that the x,y is on.
     * 
     * @param x
     * @param y
     * @param edges
     * @return
     */
    Edge findOnEdge(int x, int y, Set<Edge> edges)
    {
	Edge edgeFound = null;
	int screenX = LocationUtil.getScreenLocationX(x);
	int screenY = LocationUtil.getScreenLocationY(y);
	for (Edge edge : edges)
	{
	    int fromX = LocationUtil.getScreenLocationX(edge.getFromNode()
		    .getX());
	    int fromY = LocationUtil.getScreenLocationY(edge.getFromNode()
		    .getY());
	    int toX = LocationUtil.getScreenLocationX(edge.getToNode().getX());
	    int toY = LocationUtil.getScreenLocationY(edge.getToNode().getY());
	    if (LocationUtil.distanceToPoint(screenX, screenY, fromX, fromY) < data
		    .getScreenSelectionDistance())
	    {
		return edge;
	    }
	    double distance = LocationUtil.distanceToSegment(screenX, screenY,
		    fromX, fromY, toX, toY);
	    if (distance < data.getScreenSelectionDistance())
	    {
		edgeFound = edge;
	    }
	}
	return edgeFound;
    }

    String getTruckStatus(long id)
    {
	String result = "";
	for (Truck truck : data.getTrucks())
	{
	    if (truck.getId() == id)
	    {
		result = truck.getStatus().getStatusId();
		break;
	    }
	}
	return result;
    }
}
